﻿using System;
using System.Reflection;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Textual control property bound to a business object field (see: <see cref="IBindableField"/>).
    /// </summary>
    internal class TextualPropertyBinder : IControlPropertyBinder
    {
        bool convertEmptyStringToNull;
        bool onErrorTryDecimalParserForIntegers;

        #region Contructors.

        /// <summary>Initializes a new instance of <b>TextualPropertyBinder</b> class.</summary>
        public TextualPropertyBinder()
        {
            this.convertEmptyStringToNull = true;
        }

        /// <summary>Initializes a new instance of <b>TextualPropertyBinder</b> class.</summary>
        /// <param name="onErrorTryDecimalParserForIntegers">Indicates whether to retry integer parsing via parser for <see cref="System.Decimal"/> values when error occurs.
        /// Work-around for ADO.NET issue which maps NUMBER(x, 0) types to decimal. Especially usefull for lookup controls.</param>
        public TextualPropertyBinder(bool onErrorTryDecimalParserForIntegers)
        {            
            this.convertEmptyStringToNull = true;
            this.onErrorTryDecimalParserForIntegers = onErrorTryDecimalParserForIntegers;
        }

        #endregion

        #region GetValue.

        /// <summary>
        /// Gets the value stored in the control.
        /// </summary>
        /// <param name="control">Control.</param>
        /// <param name="propertyName">Control property.</param>
        /// <param name="dataType">Type of the value that the method will return.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format the bound values.</param>
        /// <param name="errorCode">A value indicating the type of error encountered during parsing.</param>
        /// <returns>Value stored in the control. <b>null</b> if control is empty.</returns>
        public object GetValue(IBoundControl control, string propertyName, Type dataType, IMessageFormatter formatter, out ParseError errorCode)
        {
            errorCode = ParseError.AllOk;

            PropertyInfo controlProperty = control.GetType().GetProperty(propertyName);
            string textualValue = controlProperty.GetValue(control, null) as string;
            if (textualValue == null)
                return null;

            textualValue = textualValue.Trim();
            if (this.convertEmptyStringToNull && textualValue == "")
                return null;

            object val = null;
            if (TypeUtil.IsText(dataType))
            {
                val = textualValue;
            }
            else if (TypeUtil.IsInteger(dataType))
            {
                val = NumberParser.ParseLong(textualValue, formatter);
                if (val == null && this.onErrorTryDecimalParserForIntegers)
                {
                    decimal? decValue = NumberParser.ParseDecimal(textualValue, formatter);
                    if (decValue.HasValue)
                    {
                        decimal truncedDec = decimal.Truncate(decValue.Value);
                        bool isInteger = (truncedDec == decValue.Value);
                        if (isInteger)
                            val = decimal.ToInt64(truncedDec);
                    }
                }

                if (val == null)
                    errorCode = ParseError.WrongIntegerFormat;
            }
            else if (TypeUtil.IsFloatingPointNumber(dataType))
            {
                val = NumberParser.ParseDecimal(textualValue, formatter);
                if (val == null)
                    errorCode = ParseError.WrongNumberFormat;
            }
            else if (TypeUtil.IsDate(dataType))
            {
                try { val = DateTime.ParseExact(textualValue, formatter.DateFormat, formatter.DateProvider); }
                catch { errorCode = ParseError.WrongDateFormat; }
            }

            return val;
        }

        #endregion

        #region SetValue.

        /// <summary>
        /// Sets and display the given value.
        /// </summary>
        /// <param name="control">Control.</param>
        /// <param name="propertyName">Control property.</param>
        /// <param name="val">Value to be displayed by the control.</param>
        /// <param name="formatter">The <see cref="IMessageFormatter"/> object used to format the bound values.</param>
        public void SetValue(IBoundControl control, string propertyName, object val, IMessageFormatter formatter)
        {
            PropertyInfo property = control.GetType().GetProperty(propertyName);
            string s = formatter.Format(val);
            property.SetValue(control, s, null);
        }

        #endregion
    }
}